/**
 * jUtils - Utilities library used internaly by jClass
 */
window.jUtils = {
    /**
     * copies all properties from sourceObj to targetObj
     * @param targetObj object
     * @param sourceObj object
     */
    mergeObjects : function(targetObj, sourceObj) {
        for(var obj in sourceObj) {
            if (obj === 'prototype') { continue; }
            if (typeof sourceObj[obj] !== 'object') {
                targetObj[obj] = sourceObj[obj]
            } else {
                if (typeof targetObj[obj] === 'undefined') { targetObj[obj] = {}; }
                jUtils.mergeObjects(targetObj[obj], sourceObj[obj]);
            }
        }
    },
    /**
     * Compiles the given object to a new method that can be instantiated
     * @param body Object
     * @param extending jClass
     * @return string
     */
    objectToMethod : function(body, extending, isInterface) {
        var hasConstructor = false, hasOverloads = false, overloads = [], hasStatics = false, publicMembers = new Array();
        var tmp = "function() {",
        handleModifiers = function(name, obj, type) {
            switch(jUtils.getType(obj)) {
                case 'overload' :
                    if (!hasOverloads) { tmp += 'var overloads = [];\n'; }
                    var overload = function() { if (overloads['%name%'][arguments.length]) { return overloads['%name%'][arguments.length].apply(this, arguments);
                        } else { if (arguments.length === 0) { return null; } else { var bestMatch;
                        for (var i = arguments.length; i >= 0; i--) { if (overloads['%name%'][i]) { bestMatch = overloads['%name%'][i]; break; }}
                        if (typeof bestMatch === 'undefined') { for (var j in overloads['%name%']) { bestMatch = overloads['%name%'][j]; break; } }
                        return overloads['%name%'][i].call(this, arguments);}
                        }};

                    tmp += 'overloads["' + name + '"] = ' + jUtils.serialize(obj.methods) + ";\n";
                    tmp += (type == 'public' ? 'this.' : 'var ') + name + " = " + jUtils.getBody(overload).replace(new RegExp("%name%", "ig"), name) +";\n";
                    hasOverloads = true;
                    return true;
                    break;
                case 'static' :
                    if (!hasStatics) { tmp += 'var _statics_ = [];\n'; }
                    tmp += "_statics_[\"" + name + "\"] = " + jUtils.serialize(obj.object) + ";END;\n";
                    hasStatics = true;
                    return true;
                    break;
                case 'getset' :
                    var get = obj.methods.get;
                    var set = obj.methods.set;
                    tmp += "var _" + name + " = '';";
                    if (get) { tmp += "var _get" + name + " = " + jUtils.getBody(get) + ";\n";
                        if (!IE) {
                            tmp += "Object.prototype.__defineGetter__.call(this, '" + name +"', _get" + name +");\n";
                        } else {
                            tmp += (type == 'public' ? 'this.get' : 'var get') + name.substr(0,1).toUpperCase() + name.substr(1) + " = _get" + name + ";\n";
                        }
                    }
                    if (set) {tmp += "var _set" + name + " = " + jUtils.getBody(set) + ";";
                        if (!IE) {
                            tmp += "Object.prototype.__defineSetter__.call(this, '" + name +"', _set" + name +");";
                        } else {
                            tmp += (type == 'public' ? 'this.set' : 'var set') + name.substr(0,1).toUpperCase() + name.substr(1) + " = _set" + name + ";\n";
                        }
                    }
                    return true;
                    break;
                default :
                    return false;
                    break;
            }
        }

        if (extending) {
                var superBody = jUtils.getBody(extending).replace(/_statics_\.(.[^ ]*) =/g, '_statics_["$1"] =');
                tmp += "\nvar _baseClass_ = " + superBody.replace(/var _publicMembers = \[(.*)\];/g, '') + ".call(this, arguments);\n";
                tmp += "\nvar _super = function() {};";
                var publics = jUtils.getPublicDeclarations(superBody);
                for (var i = 0; i < publics.length; i++) {
                    if (publics[i] != '') {
                        tmp += "\n_super." + publics[i] + " = this[\"" + publics[i] + "\"];\n";
                        publicMembers.push(publics[i]);
                    }
                }

        }
        if (IE) {
            if (body['constructor'] != Object.prototype.constructor) {
                if (!handleModifiers('_constructor', body['constructor'], 'private'))
                        tmp += "\nvar _constructor = " + jUtils.serialize(body['constructor']) + ";\n";

                    hasConstructor = true;
            }
        }
        for(var obj in body) {
            if (obj === 'prototype') {
                continue;
            }
            switch (obj) {
                case 'constructor' :
                    if (!handleModifiers('_constructor', body[obj], 'private'))
                        tmp += "\nvar _constructor = " + jUtils.serialize(body[obj]) + ";\n";

                    hasConstructor = true;
                    break;
                case 'public' :
                    for (var publicmethod in body[obj]) {
                        if (!handleModifiers(publicmethod, body[obj][publicmethod], 'public')) {
                            tmp += "var " + publicmethod + " = " + jUtils.serialize(body[obj][publicmethod]) + ";\nthis." + publicmethod + " = " + publicmethod + ";\n";
                        }
                        publicMembers.push(publicmethod);
                    }
                    break;
                case 'private' :
                    for (var privatemethod in body[obj]) {
                        if (!handleModifiers(privatemethod, body[obj][privatemethod], 'private')) {
                            tmp += "var " + privatemethod + " = " + jUtils.serialize(body[obj][privatemethod]) + ";\n";
                        }
                    }
                    break;
                case 'static' :
                    for (var staticmethod in body[obj]) {
                        if (!hasStatics) { tmp += 'var _statics_ = [];\n'; }
                        tmp += "_statics_[\"" + staticmethod + "\"] = " + String(jUtils.serialize(body[obj][staticmethod])) + ";END;\n";
                        hasStatics = true;
                    }
                    break;
                default :
                    if (!handleModifiers(obj, body[obj], 'public')) {
                        tmp += "var " + obj +" = " + jUtils.serialize(body[obj]) + ";\nthis." + obj + " = " + obj + ";\n";
                    }
                    publicMembers.push(obj)
                    break;
            }
        }
        tmp += "var _publicMembers = ['"+ publicMembers.join("','") +"'];\n";
        if (hasConstructor) {
            tmp += "END; _constructor.apply(this, arguments);\n";
        }
        if (isInterface === true) {
            tmp += "if (arguments.length != 1 && arguments[1] != 'check') throw new Error('Cannot instantiate an interface');"
        }
        tmp += "}";
        return tmp.replace(/Silent\(['"](.?[^"']+)['"]\)/g, '$1');
    },
    /**
     * Parses the method body to find static declarations, removes them and adds them to the method prototype
     * @param method function or string
     * @param inherited object of inherited static methods
     * @return object
     */
    handleStatics : function(method, inherited, isInterface) {
        if (typeof method === 'function') {
            method = String(jUtils.getBody(method));
        }
        if (typeof method === 'object') {
            method = jUtils.objectToMethod(method, undefined, isInterface);
        }

        var statics = method.replace(/_statics_\.(.[^ ]*) =/g, '_statics_["$1"] =').match(/_statics_\[\"(.[^"\]]*)\"\] = (((?!END;).)*)END;/g);
        method = method.replace(/_statics_\[\"(.[^"\]]*)\"\] = (((?!END;).)*)END;\n/g, '').replace(/var _statics_ = \[\];\n/g, "");
        eval("var tempObj = " + method);
        if (statics) {
            for (var i = 0; i < statics.length; i++) {
                eval("tempObj." + statics[i].replace(/_statics_\["(.[^"\]]*)"\] =/, "$1 =").replace("END;",''))
            }
        }
        if (typeof tempObj._extending === 'undefined') { tempObj._extending = new Array(); }
            if (inherited) {
                for (var x in inherited) {
                    switch (x) {
                        case "_extending" :  break;
                        case "_typeId" :
                            tempObj._extending = [inherited._typeId].concat(inherited._extending);
                        break;
                        default:
                            eval("tempObj." + x + " = " + jUtils.serialize(inherited[x]));
                            break;
                    }
                }
            }
        var id = Math.round(new Date().getTime() * Math.random()) - method.length;
        if (tempObj._typeId === undefined) { tempObj._typeId = id; }
        return tempObj;
    },
    getPublicDeclarations : function(cls) {
        var res = new Array(), body = '';
        if (typeof cls == 'string') {
            body = cls;
        } else {
            body = jUtils.getBody(cls);
        }
        var publics = body.match(/_publicMembers = \[(.*)\];/g);
        if (publics) {
            publics = publics[0].match(/"(.[^"]*)"/g);
            for (var i = 0; i < publics.length; i++) {
                var method = publics[i].replace(/"/g,'');
                if (['prototype','_prototype','isImplementationOf', 'isInstanceOf'].indexOf(method) == -1) {
                    res.push(method)
                }
            }
        }
        return res;
    },
    /**
     * Checks if body implments all methods and properties declared in the specified interface(s)
     * @param interfaceList array of jInterface objects
     * @param body jClass
     * @return boolean
     */
    validateInterface : function(interfaceList, body) {
        
        var isValid = function(interf, obj) {
            if (interf._typeName !== 'jInterface') {
                throw new TypeError("Not an interface");
            }
            var tmp = new interf('check');
            for(var method in tmp) {
                if (_.RESERVED.indexOf(method) != -1) { continue; }
                // implemented in class
                if ((typeof obj[method] === 'undefined' || typeof obj[method] != typeof tmp[method]) &&
                    (typeof obj['public'] !== 'undefined' && (typeof obj['public'][method] === 'undefined' || typeof obj['public'][method] != typeof tmp[method])) && !findInParent(method, tmp[method])) {
                            throw new Error("Interface member (" + method + ") not implemented by class");
                }                
                // implemented in parent
                var met = typeof obj[method] !== 'undefined' ? obj[method] : (obj['public'] && obj['public'][method] ? obj['public'][method] : '');
                if (typeof met === 'function') {
                        var methodArguments = jUtils.getBody(met).match(/function[ ]{0,}\(([a-zA-Z0-9$, ]{0,})\)[ ]{0,}\{[ ]{0,}\}/i)[1];
                        var maCount = methodArguments != '' ? methodArguments.split(',').length : 0;
                        var interfaceArguments = jUtils.getBody(tmp[method]).match(/function[ ]{0,}\(([a-zA-Z0-9$, ]{0,})\)[ ]{0,}\{[ ]{0,}\}/i)[1];
                        var iaCount = interfaceArguments != '' ? interfaceArguments.split(',').length : 0;
                        if (maCount != iaCount) {
                            throw new Error("Interface member (" + method + ") arguments count does not match implementation. (Expected " + iaCount + ", got " + maCount + ")");
                        }
                }
            }
       };
       
       var parent  = arguments[2] || '';
       var publics = jUtils.getPublicDeclarations(parent);
       var findInParent = function(methodName, methodType) {
           if (publics.indexOf(methodName) != -1) {
                var methodArguments = jUtils.getBody(methodType).match(/function[ ]{0,}\(([a-zA-Z0-9$, ]{0,})\)[ ]{0,}\{[ ]{0,}\}/i)[1];
                var maCount = methodArguments != '' ? methodArguments.split(',').length : 0;
                var match = jUtils.getBody(parent).match(new RegExp("var " + methodName + " = function[ ]{0,}\\(([a-zA-Z0-9$, ]{0,})\\)", "ig"));
                if (match) {
                    var args = String(match[0]).match(/function[ ]{0,}\(([a-zA-Z0-9$, ]{0,})\)/i)[1];
                    var count = args != '' ? args.split(',').length : 0;
                    if (maCount != count) {
                        throw new Error("Interface member (" + methodName + ") arguments count does not match implementation. (Expected " + maCount + ", got " + count + ")");
                    }
                }
                return true;
           } else {
               return false;
           }
       }

       for (var i = 0; i < interfaceList.length; i++) {
            isValid(interfaceList[i], body)
       }

       return true;
    },
    /**
     * Compares given object and returns true if it is of type compareClass
     * @param object jClass
     * @param compareClass jClass or jInterface
     */
    typeOf : function(object, compareClass) {
          if (typeof object === 'object' && typeof object.prototype !== 'undefined') {
               return object.prototype.typeOf == compareClass.prototype.typeOf && object.prototype.type == compareClass.prototype.type;
          } else {
              if (object && compareClass) {
                return object.prototype == compareClass.prototype;
              } else {
                  return false;
              }
          }
    },
    /**
     * Returns object jClass type
     * @param object jClass instance
     */
    getType : function(object) {
        if (typeof object !== 'undefined' && (typeof object.prototype !== 'undefined' || typeof object.typeOf !== 'undefined')) {
            if (jUtils.isDefined(object.typeOf)) {
                return object.typeOf
            } else if(jUtils.isDefined(object.prototype.typeOf)) {
                return object.prototype.typeOf;
            } else {
                return typeof object;
            }
        }
        return 'object';
    },
    compactArray : function(first, second) {
        var r = new Array(), tmp = first.concat(second);
        for(var i = 0, n = tmp.length; i < n; i++) {
            if (tmp[i] === undefined || r.indexOf(tmp[i]) != -1 || tmp[i] === '') { continue; }
            r.push(tmp[i]);
        }
        return r;
    },
    isInstanceOf : function(cls) {
        if (this._prototype && this._prototype.extending) {
            if (cls._typeId) {
                return this._prototype.extending.indexOf(cls._typeId) != -1;
            }
        }
        return false;
    },

    isImplementationOf : function(cls) {
        if (this._prototype && this._prototype.implementing) {
            if (cls._typeId) {
                return this._prototype.implementing.indexOf(cls._typeId) != -1;
            }
        }
        return false;
    },
    clone : function(obj) {
        function dummy() {}
        dummy.prototype = obj;
        return new dummy();
    },
    /**
     * Checks if all given arguments are defined.
     * @return boolean
     */
    isDefined : function() {
        for (var i = 0; i < arguments.length; i++) {
            if (typeof arguments[i] === 'undefined') {
                return false;
            }
        }
        return true;
    },
    /**
     * Checks if all given arguments are undefined
     * @return boolean
     */
    isUndefined : function() {
        for (var i = 0; i < arguments.length; i++) {
            if (typeof arguments[i] === 'undefined') {
                return true;
            }
        }
        return false;
    },
    /**
     * Returns the body of the given function
     * @param fct function
     * @return string
     */
    getBody : function(fct) {
        if (fct === undefined) return ';';
        if (typeof fct.toSource === 'function') {
            return fct.toSource().replace(/^\(/, "").replace(/\)$/, "");
        } else {
            return fct.toString().replace(/\n/g, '');
        }
    },
    /**
     * Returns defaultVal if obj is undefined
     * @param obj object
     * @param defaultVal object
     * @return object if object != undefined or defaultVal if not
     */
    defaultValue : function(obj, defaultVal) {
        if (typeof obj === 'undefined') {
            return defaultVal;
        }
        return obj;
    },
    /**
     * Pads string
     * @param input string Text to pad
     * @param count int Target length
     * @param left boolean Left if true, Right if false
     * @param pad string Character to pad with, Default is space
     * @return string
     */
    padString : function(input, count, left, pad) {
        if (typeof pad === 'undefined') pad = ' ';
        if (typeof left === 'undefined') left = true;
        if (typeof count === 'undefined') count = input.length;
        while (input.length < count)
            input = left ? pad + input : input + pad;
        return input;
    },
    /**
     * Serialize given object
     * @param theObject object
     * @return JSON
     */
    serialize : function(theObject) {
        if (typeof theObject !== 'undefined' && typeof theObject.toSource !== 'undefined' && typeof theObject.callee !== 'undefined') {
          return theObject.toSource();
       }
       switch (typeof theObject) {
          case 'number': case 'boolean': case 'function': return theObject;
          case 'string': return '"' + theObject.replace(/"/mg, "'") + '"';
          case 'object':
              var str;
              if (theObject.constructor === Array || typeof theObject.callee !== 'undefined') {
                  str = '[';
                  var i, len = theObject.length;
                  for (i = 0; i < len-1; i++) { str += jUtils.serialize(theObject[i]) + ','; }
                  str += jUtils.serialize(theObject[i]) + ']';
              } else {
                  str = '{';
                  var key;
                  for (key in theObject) { str += key + ':' + jUtils.serialize(theObject[key]) + ','; }
                  str = str.replace(/\,$/, '') + '}';
              }
              return str;
          default: return '""';
          }
    }
};